/*
@file	LineSegment.cpp
@author	Saume
*/

#include "LineSegment.h"
#include "Circle.h"

LineSegment::LineSegment() {
	from = new Vector(0.0f, 0.0f, 0.0f);
	to = new Vector(0.0f, 0.0f, 0.0f);
	CalculateNormals();
	CalculateSlope(); // to set isLinearX
}

LineSegment::LineSegment(float xFrom, float yFrom, float xTo, float yTo) {
	from = new Vector(xFrom, yFrom, 0.0f);
	to = new Vector(xTo, yTo, 0.0f);
	CalculateNormals();
	CalculateSlope(); // to set isLinearX
}

LineSegment::LineSegment(const Vector* const vFrom, const Vector* const vTo) {
	from = new Vector(*vFrom);
	to = new Vector(*vTo);
	CalculateNormals();
	CalculateSlope(); // to set isLinearX
}

/*
Calculates both normals for the segment and puts them in the appropriate vectors.
*/
void LineSegment::CalculateNormals() {
	float dx = to->x - from->x;
	float dy = to->y - from->y;
	normal = new Vector(-dy, dx, 0.0f);
	normalNeg = new Vector(dy, -dx, 0.0f);
	return;
}

/*
Returns the slope of the line, so a in (y = ax + b).
*/
float LineSegment::CalculateSlope() const {
	if (IsVerticalLine()) { // would divide by 0
		return 0;
	}
	return ((to->y - from->y) / (to->x - from->x));
}

/*
Returns wether the line segment is vertical or not.
*/
bool LineSegment::IsVerticalLine() const {
	return FloatEquals(to->x - from->x, 0.0f);
}

/*
Returns the intercept of the line, so b in (y = ax + b).
*/
float LineSegment::CalculateIntercept() const {
	return (from->y - CalculateSlope() * from->x);
}

/*
Returns the collision point of the LineSegment with l.
It will return the value regardless of wether it is in the scope of the LineSegments or not.
It will return 0 if there is not intersection point.
If the two lines are parallel, it will return 0.
*/
Vector* LineSegment::CollisionPoint(const LineSegment* const l) const {
	Vector* point = new Vector();
	
	float a = CalculateSlope();
	float aL = l->CalculateSlope();
	float b = CalculateIntercept();
	float bL = l->CalculateIntercept();

	if (FloatEquals(a, aL)) { // possibly parallel
		if (FloatEquals(a, 0.0f)) { // but possibly not, in the case the slopes are 0
			if (IsVerticalLine() == l->IsVerticalLine()) { // they are both in the same axis so they are actually parallel
				delete point;
				return 0;
			}
		} else { // they truly are parallel
			delete point;
			return 0;
		}
	}

	// if they make it this far, it means no return was called
	// as such, they are not parallel and WILL intersect, if projected to infinity
	// at this point, there is one possible case where a == aL
	// when a == aL, a division by 0 would occur, so this must be treated
	// that is, if they are perpendicular vertical and horizontal lines
	if (FloatEquals(a, aL)) {
		if (IsVerticalLine()) { // this LineSegment is vertical, so l is horizontal
			point->x = from->x; // any x point in the LineSegment will work
			point->y = l->from->y; // any y point in l will work
		} else { // this LineSegment is horizontal, so l is vertical
			point->x = l->from->x;
			point->y = from->y;
		}
	} else {
		if (IsVerticalLine()) { // special treatment for vertical lines
			point->x = from->x; // any x point in the LineSegment will work
			point->y = aL * point->x + bL; // y = a x + b (on l, as it is not vertical)
		} else { // this LineSegment isn't vertical
			if (l->IsVerticalLine()) { // special treatment for verticla lines
				point->x = l->from->x; // any x point in l will work
			} else { // regular check
				// equalize the two equations to find the y value where they intersect
				// a x + b = aL xL + bL (where x == xL)
				point->x = (bL - b) / (a - aL); // x = (bL - b) / (a - aL)
			}

			// as this LineSegment is not vertical, we can use the regular equation on this Segment
			// to find the intersection point between the two lines
			point->y = a * point->x + b; // y = a x + b
		}
	}

	return point;
}

bool LineSegment::IntersectsWith(const LineSegment* const l) const {
	float a = CalculateSlope();
	float aL = l->CalculateSlope();
	float b = CalculateIntercept();
	float bL = l->CalculateIntercept();
	
	if (FloatEquals(a, aL)) { // possibly parallel
		if (FloatEquals(a, 0.0f)) { // but possibly not, in the case the slopes are 0
			if (IsVerticalLine() == l->IsVerticalLine()) { // they are both in the same axis so they are actually parallel
				if (PointIsInSegment(l->from->x, l->from->y) || PointIsInSegment(l->to->x, l->to->y)) {
					return true; // they touch
				} else {
					return false;
				}
			}
		} else { // they truly are parallel
			if (FloatEquals(b, bL)) { // and overlapping
				if (PointIsInSegment(l->from->x, l->from->y) || PointIsInSegment(l->to->x, l->to->y)) {
					return true; // they are in the same scope
				} else {
					return false; // parallel and overlapping, but not in the same scope
				}
			} else {
				return false;// parallel and not overlapping
			}
		}
	}

	// if they make it this far, it means no return was called
	// as such, they are not parallel and WILL intersect, if projected to infinity
	float xPoint; // x value of the intersection point
	float yPoint; // x value of the intersection point
	
	// at this point, there is one possible case where a == aL
	// when a == aL, a division by 0 would occur, so this must be treated
	// that is, if they are perpendicular vertical and horizontal lines
	if (FloatEquals(a, aL)) {
		if (IsVerticalLine()) { // this LineSegment is vertical, so l is horizontal
			xPoint = from->x; // any x point in the LineSegment will work
			yPoint = l->from->y; // any y point in l will work
		} else { // this LineSegment is horizontal, so l is vertical
			xPoint = l->from->x;
			yPoint = from->y;
		}
	} else {
		if (IsVerticalLine()) { // special treatment for vertical lines
			xPoint = from->x; // any x point in the LineSegment will work
			yPoint = aL * xPoint + bL; // y = a x + b (on l, as it is not vertical)
		} else { // this LineSegment isn't vertical
			if (l->IsVerticalLine()) { // special treatment for verticla lines
				xPoint = l->from->x; // any x point in l will work
			} else { // regular check
				// equalize the two equations to find the y value where they intersect
				// a x + b = aL xL + bL (where x == xL)
				xPoint = (bL - b) / (a - aL); // x = (bL - b) / (a - aL)
			}

			// as this LineSegment is not vertical, we can use the regular equation on this Segment
			// to find the intersection point between the two lines
			yPoint = a * xPoint + b; // y = a x + b
		}
	}

	// now that the intersection point has been found, 
	// check if x and y are within the scopes of the segments
	if (PointIsInSegment(xPoint, yPoint) && l->PointIsInSegment(xPoint, yPoint)) {
		return true;
	}

	// if it makes it this far without a return, it means they do not intersect
	return false;
}

bool LineSegment::IntersectsWith(const Circle* const cir) const {
	// a = slope, b = intercept
	// for the circle, c = x coordinate, d = y coordinate, r = radius
	float a = CalculateSlope();
	float b = CalculateIntercept();
	float c = cir->GetX();
	float d = cir->GetY();
	float r = cir->GetRadius();

	// special treatment if it is a vertical line
	if (IsVerticalLine()) { // if the line segment is a vertical line
		// calculate the distance between 
		Vector lineToCircle;// distance between the starting point of the segment and the center of the circle
		lineToCircle.x = cir->GetX() - from->x;
		lineToCircle.y = cir->GetY() - from->y;
		Vector dir;
		dir.x = to->x - from->x;
		dir.y = to->y - from->y;
		float dist = lineToCircle.Length() / dir.Length();
		// if the line is within the radius of the circle
		if (dist <= cir->GetRadius()) {
			// y = d +- SqRt(r^2 - (x - c)^2)
			float xPoint = from->x;
			float yPoint = d + sqrt(cir->GetRadiusSq() - (xPoint - c) * (xPoint - c));
			if (PointIsInSegment(xPoint, yPoint)) {
				return true;
			} else {
				float xPoint = from->x;
				float yPoint = d - sqrt(cir->GetRadiusSq() - (xPoint - c) * (xPoint - c));
				if (PointIsInSegment(xPoint, yPoint)) {
					return true;
				}
				else {
					return false;
				}
			}
		} else { // the line is farther away in distance than the radius of the circle, and will never touch it
			return false;
		}
	}

	// otherwise, resume normal treatment
	// (a^2 + 1) * x^2 + 2(ab - ad - c) * x + (d^2 - r^2 + c^2 - 2bd + b^2) = 0
	// put this in the quadratic form of Ax^2 + Bx + C = 0
	float aDiscr = a * a + 1;
	float bDiscr = 2 * (a * b - a * d - c);
	float cDiscr = d * d - r * r + c * c - 2 * b * d + b * b;
	// calculate the discriminant, B^2 - 4AC
	float discriminant = CalculateDiscriminant(aDiscr, bDiscr, cDiscr);
	// if the discriminant is below 0, it will never touch the circle
	if (discriminant < 0) {
		return false;
	} else { // otherwise, it will at some point
		if (discriminant == 0) { // tangent intersection
			// x = -B / 2A, tangent point
			float xPoint = -bDiscr / (2 * aDiscr);
			float yPoint = a * xPoint + b;
			if (PointIsInSegment(xPoint, yPoint)) {
				return true;
			} else {
				return false;
			}
		} else { // intersects in 2 points with the circle
			// x = ( -B +- SqRt(discriminant) ) / 2A, two points
			float xPoint = (-bDiscr + sqrt(discriminant)) / (2 * aDiscr);
			float yPoint = a * xPoint + b;
			if (PointIsInSegment(xPoint, yPoint)) {
				return true;
			}
			else {
				float xPoint = (-bDiscr - sqrt(discriminant)) / (2 * aDiscr);
				float yPoint = a * xPoint + b;
				if (PointIsInSegment(xPoint, yPoint)) {
					return true;
				} else {
					return false;
				}
			}
		}
	}
}

float LineSegment::CalculateDiscriminant(float a, float b, float c) const {
	return b * b - 4 * a * c;
}

/*
Determines if the given point is within scope of the LineSegment.
Considers the point is already on the line.
*/
bool LineSegment::PointIsInSegment(float xVal, float yVal) const {
	// get the correct values for check
	float xMax, xMin, yMax, yMin;
	if (from->x > to->x) {
		xMax = from->x;
		xMin = to->x;
	} else {
		xMax = to->x;
		xMin = from->x;
	}
	if (from->y > to->y) {
		yMax = from->y;
		yMin = to->y;
	} else {
		yMax = to->y;
		yMin = from->y;
	}

	// check if x and y are within the scope of the segment
	if (xVal >= xMin && xVal <= xMax) {
		if (yVal >= yMin && yVal <= yMax) {
			// we have to check the y values as well, in case it is an intersection of horizontal or vertical line
			return true;
		}
	}
	return false;
}

/*
Returns a new LineSegment, extended by given length on each end.
*/
LineSegment* LineSegment::Extend(float len) const {
	Vector* lFrom = new Vector(*from);
	Vector* lTo = new Vector(*to);
	float slope = CalculateSlope();

	if (slope > 0) {
		lFrom->x -= len;
		lTo->x += len;
		lFrom->y -= len * slope;
		lTo->y += len * slope;
	} else {
		if (slope < 0) {
			lFrom->x += len;
			lTo->x -= len;
			lFrom->y += len * slope;
			lTo->y -= len * slope;
		} else {
			// the slope is 0, so either horizontal or vertical line
			if (IsVerticalLine()) {
				if (lFrom->y < lTo->y) {
					// the start point is lower in value than the end point
					lFrom->y -= len;
					lTo->y += len;
				} else {
					// the start point is higher in value than the end point
					lFrom->y += len;
					lTo->y -= len;
				}
			} else {
				if (lFrom->x < lTo->x) {
					// the start point is lower in value than the end point
					lFrom->x -= len;
					lTo->x += len;
				}
				else {
					// the start point is higher in value than the end point
					lFrom->x += len;
					lTo->x -= len;
				}
			}
		}
	}
	
	// create the new LineSegement from the calculate positions
	LineSegment *l = new LineSegment(lFrom, lTo);
	
	// delete the temporary vectors used to construct the LineSegment
	delete lFrom, lTo;

	return l;
}

void LineSegment::Display() const {
	std::cout << "(" << from->x << ", " << from->y << ") -> (" << to->x << ", " << to->y << ")";
	return;
}

LineSegment::~LineSegment() {
	delete from, to, normal, normalNeg;
}

bool operator==(const LineSegment& l1, const LineSegment& l2) {
	float dx = l2.GetFrom()->x - l1.GetFrom()->x;
	float dy = l2.GetFrom()->y - l1.GetFrom()->y;
	float dtox = l2.GetTo()->x - l1.GetTo()->x;
	float dtoy = l2.GetTo()->y - l1.GetTo()->y;
	dx = fabs(dx);
	dy = fabs(dy);
	dtox = fabs(dtox);
	dtoy = fabs(dtoy);

	return dx <= EPSILON_DBL && dy <= EPSILON_DBL && dtox <= EPSILON_DBL && dtoy <= EPSILON_DBL;
}